{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1. GBDT概述\n",
    "\n",
    "\n",
    "　　　　GBDT也是集成学习Boosting家族的成员，但是却和传统的Adaboost有很大的不同。回顾下Adaboost，我们是利用前一轮迭代弱学习器的误差率来更新训练集的权重，这样一轮轮的迭代下去。GBDT也是迭代，使用了前向分布算法，但是弱学习器限定了只能使用CART回归树模型，同时迭代思路和Adaboost也有所不同。\n",
    "\n",
    "　　　　在GBDT的迭代中，假设我们前一轮迭代得到的强学习器是$f_{t−1}(x)$, 损失函数是$L(y,f_{t−1}(x))$, 我们本轮迭代的目标是找到一个CART回归树模型的弱学习器$h_t(x)$，让本轮的损失损失$L(y,f_t(x))=L(y,f_{t−1}(x)+h_t(x))$最小。也就是说，本轮迭代找到决策树，要让样本的损失尽量变得更小。\n",
    "\n",
    "　　　　GBDT的思想可以用一个通俗的例子解释，假如有个人30岁，我们首先用20岁去拟合，发现损失有10岁，这时我们用6岁去拟合剩下的损失，发现差距还有4岁，第三轮我们用3岁拟合剩下的差距，差距就只有一岁了。如果我们的迭代轮数还没有完，可以继续迭代下面，每一轮迭代，拟合的岁数误差都会减小。\n",
    "\n",
    "　　　　从上面的例子看这个思想还是蛮简单的，但是有个问题是这个损失的拟合不好度量，损失函数各种各样，怎么找到一种通用的拟合方法呢？\n",
    "    \n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2. GBDT的负梯度拟合\n",
    "\n",
    "\n",
    "在上一节中，我们介绍了GBDT的基本思路，但是没有解决损失函数拟合方法的问题。\n",
    "\n",
    "针对这个问题，大牛Freidman提出了用损失函数的负梯度来拟合本轮损失的近似值，进而拟合一个CART回归树。\n",
    "\n",
    "第$t$轮的第$i$个样本的损失函数的负梯度表示为\n",
    "\n",
    "$$r_{ti}=−[ \\frac {∂L(y_i,f(x_i)))}{∂f(x_i)}]_{f(x)=f_{t−1}(x)}$$\n",
    "\n",
    "利用$(x_i,r_{ti})(i=1,2,..m)$,我们可以拟合一颗CART回归树，得到了第$t$颗回归树，其对应的叶节点区域$R_{tj},j=1,2,...,J$。其中$J$为叶子节点的个数。\n",
    "\n",
    "针对每一个叶子节点里的样本，我们求出使损失函数最小，也就是拟合叶子节点最好的的输出值$c_{tj}$如下：\n",
    "$$c_{tj}=\\underbrace{ arg min}_{\\rm c} \\sum_{x_i∈R_{tj}}  L(y_i,f_{t−1}(x_i)+c)$$\n",
    "\n",
    "这样我们就得到了本轮的决策树拟合函数如下：\n",
    "$$h_t(x)=\\sum_{j=1}^Jc_{tj}I(x∈R_{tj})$$\n",
    "\n",
    "从而本轮最终得到的强学习器的表达式如下：\n",
    "$$f_t(x)=f_{t−1}(x)+\\sum_{j=1}^Jc_{tj}I(x∈R_{tj})$$\n",
    "　　　　\n",
    "通过损失函数的负梯度来拟合，我们找到了一种通用的拟合损失误差的办法，这样无轮是分类问题还是回归问题，我们通过其损失函数的负梯度的拟合，就可以用GBDT来解决我们的分类回归问题。区别仅仅在于损失函数不同导致的负梯度不同而已。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3. GBDT回归算法\n",
    "\n",
    "\n",
    "好了，有了上面的思路，下面我们总结下GBDT的回归算法。为什么没有加上分类算法一起？那是因为分类算法的输出是不连续的类别值，需要一些处理才能使用负梯度，我们后面讲。\n",
    "\n",
    "输入是训练集样本$T={(x_1,y_1),(x_2,y_2),...(x_m,y_m)}$， 最大迭代次数T, 损失函数L。\n",
    "\n",
    "输出是强学习器$f(x)$\n",
    "\n",
    "1) 初始化弱学习器\n",
    "$$f_0(x)=\\underbrace{ arg min}_{\\rm c} \\sum_{i=1}^m  L(y_i,c)$$\n",
    "\n",
    "2) 对迭代轮数$t=1,2,...T$有：\n",
    "\n",
    "　　a)对样本$i=1,2，...m$，计算负梯度\n",
    "$$r_{ti}=−[ \\frac {∂L(y_i,f(x_i)))}{∂f(x_i)}]_{f(x)=f_{t−1}(x)}$$\n",
    "　　b)利用$(x_i,r_{ti})(i=1,2,..m)$, 拟合一颗CART回归树,得到第t颗回归树，其对应的叶子节点区域为$R_{tj},j=1,2,...,J$。其中$J$为回归树t的叶子节点的个数。\n",
    "\n",
    "　　c) 对叶子区域$j =1,2,..J$,计算最佳拟合值\n",
    "$$c_{tj}=\\underbrace{ arg min}_{\\rm c} \\sum_{x_i∈R_{tj}}  L(y_i,f_{t−1}(x_i)+c)$$\n",
    "　　　　　　\n",
    "　　d) 更新强学习器\n",
    "$$f_t(x)=f_{t−1}(x)+\\sum_{j=1}^Jc_{tj}I(x∈R_{tj})$$\n",
    "\n",
    "3) 得到强学习器f(x)的表达式\n",
    "$$f(x)=f_T(x)=f_0(x)+\\sum_{t=1}^T\\sum_{j=1}^Jc_{tj}I(x∈R_{tj})$$\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4. GBDT分类算法\n",
    "\n",
    "\n",
    "这里我们再看看GBDT分类算法，GBDT的分类算法从思想上和GBDT的回归算法没有区别，但是由于样本输出不是连续的值，而是离散的类别，导致我们无法直接从输出类别去拟合类别输出的误差。\n",
    "\n",
    "为了解决这个问题，主要有两个方法，一个是用指数损失函数，此时GBDT退化为Adaboost算法。\n",
    "\n",
    "另一种方法是用类似于逻辑回归的对数似然损失函数的方法。\n",
    "\n",
    "也就是说，我们用的是类别的预测概率值和真实概率值的差来拟合损失。\n",
    "\n",
    "本文仅讨论用对数似然损失函数的GBDT分类。而对于对数似然损失函数，我们又有二元分类和多元分类的区别。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4.1 二元GBDT分类算法\n",
    "\n",
    "\n",
    "对于二元GBDT，如果用类似于逻辑回归的对数似然损失函数，则损失函数为：\n",
    "$$L(y,f(x))=log(1+exp(−yf(x)))$$\n",
    "\n",
    "其中$y∈\\{−1,+1\\}$。则此时的负梯度误差为\n",
    "$$r_{ti}=−[\\frac {∂L(y,f(x_i)))}{∂f(x_i)}]_{f(x)=f_{t−1}(x)}=y_i/(1+exp(y_if(x_i)))$$\n",
    "\n",
    "对于生成的决策树，我们各个叶子节点的最佳残差拟合值为\n",
    "$$c_{tj}=\\underbrace{ arg min}_{\\rm c} \\sum_{x_i∈R_{tj}} log(1+exp(−y_i(f_{t−1}(x_i)+c)))$$\n",
    "　　　　　\n",
    "由于上式比较难优化，我们一般使用近似值代替\n",
    "$$c_{tj}=\\sum_{x_i∈R_{tj}} r_{ti}/\\sum_{x_i∈R_{tj}} |r_{ti}|(1−|r_{ti}|)$$\n",
    "\n",
    "除了负梯度计算和叶子节点的最佳残差拟合的线性搜索，二元GBDT分类和GBDT回归算法过程相同。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4.2 多元GBDT分类算法\n",
    "\n",
    "\n",
    "多元GBDT要比二元GBDT复杂一些，对应的是多元逻辑回归和二元逻辑回归的复杂度差别。假设类别数为K，则此时我们的对数似然损失函数为：\n",
    "$$L(y,f(x))=−\\sum_{k=1}^Ky_k \\log p_k(x)$$\n",
    "\n",
    "其中如果样本输出类别为$k$，则$y_k=1$。第$k$类的概率$p_k(x)$的表达式为：\n",
    "$$p_k(x)=exp(f_k(x))/\\sum_{l=1}^K exp(f_l(x))$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**通过上式我们可以看出，我们是将类别进行one-hot编码，每个输出都要建一颗决策树，一个样本通过K个决策树，得到K个输出，在通过softmax函数，获得K个概率。**\n",
    "\n",
    "集合上两式，我们可以计算出第$t$轮的第$i$个样本对应类别$l$的负梯度误差为\n",
    "$$r_{til}=−[\\frac {∂L(y_i,f(x_i)))}{∂f(x_i)}]_{f_k(x)=f_{l,t−1}(x)}=y_{il}−p_{l,t−1}(x_i)$$\n",
    "\n",
    "观察上式可以看出，其实这里的误差就是样本$i$对应类别$l$的真实概率和$t−1$轮预测概率的差值。\n",
    "\n",
    "对于生成的决策树，我们各个叶子节点的最佳残差拟合值为\n",
    "$$c_{tjl}=\\underbrace{ arg min}_{\\rm c_{jl}} \\sum_{i=0}^{m} \\sum_{k=1} ^K L(y_k,f_{t−1,l}(x)+\\sum_{j=0}^Jc_{jl}I(x_i∈R_{tj}))$$\n",
    "\n",
    "由于上式比较难优化，我们一般使用近似值代替\n",
    "$$c_{tjl}=\\frac{K−1}{K} \\frac{\\sum_{x_i∈R_{tjl}}r_{til}}{∑_{x_i∈R_{til}}|r_{til}|(1−|r_{til}|)}$$\n",
    "\n",
    "除了负梯度计算和叶子节点的最佳残差拟合的线性搜索，多元GBDT分类和二元GBDT分类以及GBDT回归算法过程相同。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 5. GBDT常用损失函数\n",
    "\n",
    "\n",
    "这里我们再对常用的GBDT损失函数做一个总结。\n",
    "\n",
    "**对于分类算法**，其损失函数一般有对数损失函数和指数损失函数两种:\n",
    "\n",
    "a) 如果是指数损失函数，则损失函数表达式为\n",
    "$$L(y,f(x))=exp(−yf(x))$$\n",
    "\n",
    "其负梯度计算和叶子节点的最佳残差拟合参见Adaboost原理篇。\n",
    "\n",
    "b) 如果是对数损失函数，也就是前面说的二元分类和多元分类两种。\n",
    "　　　　\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**对于回归算法**，常用损失函数有如下4种:\n",
    "\n",
    "　　a)均方差，这个是最常见的回归损失函数了\n",
    "$$L(y,f(x))=(y−f(x))^2$$\n",
    "\n",
    "　　b)绝对损失，这个损失函数也很常见\n",
    "$$L(y,f(x))=|y−f(x)|$$\n",
    "\n",
    "　　对应负梯度误差为：\n",
    "$$sign(y_i−f(x_i))$$\n",
    "\n",
    "　　c)Huber损失，它是均方差和绝对损失的折衷产物，对于远离中心的异常点，采用绝对损失，而中心附近的点采用均方差。这个界限一般用分位数点度量。\n",
    "  \n",
    "  损失函数如下：\n",
    "$$L(y,f(x))= \\begin{cases} 0.5(y−f(x))^2, & {|y−f(x)|≤δ} \\\\ δ(|y−f(x)|−0.5δ), & {|y−f(x)|>δ} \\end{cases} $$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对应的负梯度误差为：\n",
    "　　　　\n",
    "$$r(y_i,f(x_i))= \\begin{cases} y_i−f(x_i), & {|y_i−f(x_i)|≤δ} \\\\ δsign(y_i−f(x_i)), & {|y_i−f(x_i)|>δ} \\end{cases} $$\n",
    "\n",
    "\n",
    "　 d) 分位数损失。它对应的是分位数回归的损失函数，表达式为\n",
    "$$L(y,f(x))=\\sum_{y≥f(x)} θ|y−f(x)|+\\sum_{y<f(x)}(1−θ)|y−f(x)|$$\n",
    "\n",
    "　　　其中θ为分位数，需要我们在回归前指定。对应的负梯度误差为：\n",
    "$$r(y_i,f(x_i))= \\begin{cases} θ, & {y_i≥f(x_i)} \\\\ θ−1, & {y_i<f(x_i)} \\end{cases} $$\n",
    "\n",
    "对于Huber损失和分位数损失，主要用于健壮回归，也就是减少异常点对损失函数的影响。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 6. GBDT的正则化\n",
    "\n",
    "\n",
    "　　　　和Adaboost一样，我们也需要对GBDT进行正则化，防止过拟合。GBDT的正则化主要有三种方式。\n",
    "\n",
    "　　　　第一种是和Adaboost类似的正则化项，即**步长(learning rate)**。定义为$ν$,对于前面的弱学习器的迭代\n",
    "$$f_k(x)=f_{k−1}(x)+h_k(x)$$\n",
    "\n",
    "　　　　如果我们加上了正则化项，则有\n",
    "$$f_k(x)=f_{k−1}(x)+νh_k(x)$$\n",
    "\n",
    "　　　　$ν$的取值范围为$0<ν≤1$。对于同样的训练集学习效果，较小的$ν$意味着我们需要更多的弱学习器的迭代次数。通常我们用步长和迭代最大次数一起来决定算法的拟合效果。\n",
    "\n",
    "　　　　第二种正则化的方式是通过**子采样比例（subsample）**。取值为(0,1]。注意这里的子采样和随机森林不一样，随机森林使用的是放回抽样，而这里是不放回抽样。如果取值为1，则全部样本都使用，等于没有使用子采样。如果取值小于1，则只有一部分样本会去做GBDT的决策树拟合。选择小于1的比例可以减少方差，即防止过拟合，但是会增加样本拟合的偏差，因此取值不能太低。推荐在[0.5, 0.8]之间。\n",
    "\n",
    "　　　　使用了子采样的GBDT有时也称作随机梯度提升树(Stochastic Gradient Boosting Tree, SGBT)。由于使用了子采样，程序可以通过采样分发到不同的任务去做boosting的迭代过程，最后形成新树，从而减少弱学习器难以并行学习的弱点。\n",
    "\n",
    " \n",
    "\n",
    "　　　　第三种是对于弱学习器即CART回归树进行**正则化剪枝**。在决策树原理篇里我们已经讲过，这里就不重复了。\n",
    "\n",
    "# 7. GBDT小结\n",
    "\n",
    "\n",
    "　　　　GBDT终于讲完了，GDBT本身并不复杂，不过要吃透的话需要对集成学习的原理，决策树原理和各种损失函树有一定的了解。由于GBDT的卓越性能，只要是研究机器学习都应该掌握这个算法，包括背后的原理和应用调参方法。目前GBDT的算法比较好的库是xgboost。当然scikit-learn也可以。\n",
    "\n",
    "　　　　最后总结下GBDT的优缺点。\n",
    "\n",
    "　　　　GBDT主要的优点有：\n",
    "\n",
    "　　　　1) 可以灵活处理各种类型的数据，包括连续值和离散值。GBDT使用的是cart树模型，可以处理连续值和离散值特征。对于连续值节点划分时，按照大于小于分割点，对于离散值，按照等于不等于划分分割点。\n",
    "\n",
    "　　　　2) 在相对少的调参时间情况下，预测的准备率也可以比较高。这个是相对SVM来说的。\n",
    "\n",
    "　　　　3）使用一些健壮的损失函数，对异常值的鲁棒性非常强。比如 Huber损失函数和Quantile损失函数。\n",
    "\n",
    "　　　　GBDT的主要缺点有：\n",
    "\n",
    "　　　　1)由于弱学习器之间存在依赖关系，难以并行训练数据。不过可以通过自采样的SGBT来达到部分并行。\n",
    "    \n",
    "　　　　2）对于稀疏矩阵，容易过拟合，比lr和fm效果要差，是因为若数据中存在假象数据（例如特征f1为1的样本，输出类别都是1），树模型没法进行过拟合处理。而lr或fm可以通过正则化，压缩w1的值。\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
